5장. 결합에도 등급이 있다
출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 제이펍 2026) | 원서: Balancing Coupling in Software Design (Manning) · 입문판·PDF 재구성
코드는 분위기만 —
switch·static·namespace같은 말은 몰라도 됩니다. 표의 '비유'와 '위험'만 봐도 충분해요.
연결(결합)에도 등급이 있다.
같은 "연결"이라도 어떤 건 위험하고 어떤 건 안전하다.
1960~70년대에 사람들이 이걸 처음으로 6단계로 줄 세웠다.
이 장은 그 6단계를 위에서 아래로 훑는다.
0. 이 장의 새 단어 (3개)
0장에 없던 말은 딱 3개다.
나머지 어려운 말(결합·상위/하위·공유 지식·캡슐화 경계·인터페이스·변경 전파·복잡한 상호작용)은 전부 0장에 있다.
막히면 0장으로 돌아가면 된다.
모듈 결합 6수준 (Module Coupling Levels)
한 문장 뜻 — 두 부품이 어떤 방식으로 연결됐는지를 위험한 순서대로 6칸으로 줄 세운 것.
일상비유 — 매운맛 단계. 같은 "매운 라면"이라도 1단계부터 6단계까지 있다. 등급을 알면 "이건 너무 맵다"를 미리 안다.
한 줄 예 —
var levels = ["콘텐츠", "공통", "외부", "제어", "스탬프", "데이터"]; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
// 왼쪽=위험(강함), 오른쪽=안전(약함)
전역 상태 (Global State)
한 문장 뜻 — 여러 부품이 동시에 읽고 쓸 수 있는 공용 저장 공간. 전역 변수·공유 파일·공유 DB가 다 여기 속한다.
일상비유 — 아파트 공동 게시판. 누구나 메모를 쓰고 지운다. 누가 언제 바꿨는지 아무도 모른다.
한 줄 예 —
shared["balance"] = 100; // 어느 부품이든 이 값을 만질 수 있음(전역)
DTO (Data Transfer Object)
한 문장 뜻 — 바깥에 넘겨주려고 일부러 따로 만든 단순 데이터 꾸러미. 속사정을 숨기는 포장지다.
일상비유 — 택배 송장. 집 안 가구 배치는 안 적고, 배달에 꼭 필요한 주소·이름만 적는다.
한 줄 예 —
public class CustomerSnapshot // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public string Name { get; init; } // 외부에 보여 줄 읽기용 값입니다.
}
(귀납 도입) 이런 적 있죠?
함수 하나 살짝 고쳤을 뿐인데 엉뚱한 모듈이 터진 적 있죠?
그런데 어떤 연결은 고쳐도 멀쩡하고, 어떤 연결은 건드리기만 해도 와르르 무너진다.
같은 "연결"인데 왜 이렇게 다를까?
옛날 사람들도 똑같이 궁금했다.
그래서 1969년에 '소프트웨어 위기'라는 말까지 나왔다.
"프로그램이 너무 얽혀서 만들기도 고치기도 비싸다"는 한탄이었다.
반세기가 지난 지금도 우리는 같은 고민을 한다.
래리 콘스탄틴과 에드워드 요던은 이 문제를 풀려고 연결을 등급으로 나눴다.
아래 코드를 보자.
// 한 부품이 다른 부품의 "숨겨둔 방"을 직접 연다
payment.ValidateInternally(card); // _ = 비공개 = 열지 말라는 표시
이건 가장 위험한 1단계다.
반대로 아래는 가장 안전한 6단계다.
var status = repo.GetStatus(id); // 필요한 값 하나만 딱 받음
둘 다 "연결"이지만 위험도가 하늘과 땅이다.
이 위험도 줄 세우기가 바로 모듈 결합 6수준이다.
이 장에서 딱 6가지만
이 장에서 딱 6가지만 — 위험한 순서로 외운다.
- 콘텐츠 — 벽 뚫고 들어옴 (가장 위험)
- 공통 — 공동 게시판 통째로 공유
- 외부 — 공동 우편함 한 칸만 공유
- 제어 — 조리법까지 지시
- 스탬프 — 명함 통째로 건넴
- 데이터 — 이름 한 단어만 (가장 안전)
위로 갈수록 서로 많이 알고(공유 지식↑), 변경이 멀리 번진다. 아래로 갈수록 서로 적게 알고, 변경이 덜 번진다. 기본값은 항상 아래쪽(데이터 결합)이다.
개념 1. 콘텐츠 결합 — 벽 뚫고 들어옴 (가장 위험)
망가지는 장면
옆집 사람이 벨도 안 누르고 내 집 벽을 뚫고 들어와 서랍에서 물건을 꺼내 간다.
내가 서랍 위치만 바꿔도 옆집 살림이 깨진다.
게다가 나는 그 사람이 들어온 줄도 모른다.
일상비유
남의 집 벽을 뚫는 행위.
문(공개 인터페이스)으로 안 들어오고 벽(비공개 속사정)을 직접 뚫는다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 벽 뚫고 들어옴 | payment._validate(card) |
숨긴 걸 바꾸면 침입자가 깨짐 |
| 문으로 들어옴(정상) | payment.Charge(card) |
안전 — 약속된 입구만 씀 |
정의 — 하위 부품이 상위 부품의 공개 인터페이스를 무시하고 비공개 속사정을 직접 건드리는 것.
다른 이름은 병리학적 결합. 나쁠 수밖에 없어서 붙은 이름이다.
예시 1 (worked — 완성예): 비공개 메서드 강제 호출
// InvoiceGenerator 가 일부러 숨겨둔(_verify_input) 메서드를
// 바깥에서 억지로 끄집어내 호출한다
var invoice = new InvoiceGenerator(); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
invoice.VerifyInput("input"); // _ = "쓰지 마세요" 표시인데 무시함
_verify_input은 소비자가 부르라고 만든 게 아니다.
이름을 바꾸거나 지우면 이 코드가 조용히 깨진다.
예시 2 (worked): 남의 서비스 DB 직접 접속
// 결제 서비스의 공식 API 를 안 쓰고, 그 서비스의 DB 에 직접 꽂는다
var rows = payment_db.Query("SELECT * FROM cards"); // 남의 속사정에 직접 침입
결제 서비스가 컬럼 이름만 바꿔도 이 코드가 터진다.
결제 서비스 작성자는 누가 자기 DB를 보고 있는지조차 모른다.
예시 3 (부분완성 — 빈칸 채우기)
아래 두 줄 중 콘텐츠 결합은 어느 쪽일까?
var a = report.BuildSqlInternally(filter); // (A)
var b = report.GetSummary(filter); // (B)
정답은 (A).
_ 로 숨긴 비공개 함수를 바깥에서 직접 부르니 벽을 뚫은 것이다.
예시 4 (독립적용): 직접 판단
마이크로서비스 A가 B의 내부 캐시 파일을 몰래 열어 읽는다.
이건 몇 단계일까?
콘텐츠 결합이다.
B의 공식 입구(API)를 무시하고 B의 속사정(캐시 파일)에 직접 손댔으니까.
미니 시나리오
"DB만 살짝 바꿨는데 왜 옆 팀 서비스가 죽지?"
→ 그 팀이 내 DB에 직접 꽂아 쓰고 있었던 것.
→ 콘텐츠 결합의 전형. 공식 API로만 주고받게 바꿔야 한다.
단순 규칙 — 남이 숨긴 것(_·private·내부 DB·내부 파일)을 직접 건드리면 콘텐츠 결합. 무조건 피한다.
개념 2. 공통 결합 — 공동 게시판 통째로
망가지는 장면
30가구가 공동 게시판 하나에 자유롭게 메모를 쓰고 지운다.
누가 어떤 메모를 바꿨는지 추적이 안 된다.
한 집이 게시판 양식을 바꾸면 30가구가 다 같이 헷갈린다.
일상비유
아파트 공동 게시판.
모두가 같은 판을 읽고 쓴다. 그게 전역 상태다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 공동 게시판 | shared["x"] = 1 |
누가 바꿨는지 추적 불가 |
| 내 메모장(격리) | local_x = 1 |
안전 — 나만 만짐 |
정의 — 여러 부품이 전역적으로 공유되는 데이터 전체를 다 같이 읽고 쓰는 것.
예시 1 (worked): 포트란 COMMON 블록 (역사 원형)
포트란에는 COMMON이라는 명령이 있었다.
여러 서브루틴이 변수 4개(ALPHA·BETA·GAMMA·DELTA)를 통째로 공유했다.
// 포트란 COMMON 을 C# 느낌으로
var shared = {"alpha": 0, "beta": 0, "gamma": 0, "delta": 0}; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
void Sub1() { shared["alpha"] += 1; } // beta 만 쓰고 싶어도
void Sub2() { shared["beta"] += 1; } // 4개 블록 전체를 알아야 함
beta 하나만 쓰고 싶어도 블록 전체를 선언해야 한다.
beta의 자료형을 바꾸면 셋 다 동시에 고쳐야 한다.
예시 2 (worked): 현대판 — 공유 파일/공유 키
// 여러 모듈이 같은 S3 파일을 동시에 읽고 쓴다
write_s3("data.Json", payload); // 모듈 A
var data = read_s3("data.Json"); // 모듈 B — 같은 파일
S3·구글 클라우드 스토리지·애저 스토리지의 같은 파일, 또는 같은 Redis 키를 여러 모듈이 만지는 게 전형이다.
예시 3 (부분완성): 같은 클래스 안 멤버 공유
같은 클래스 안 여러 메서드가 같은 멤버 변수를 만지는 것도 공통 결합일까?
public class Cart // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public void Add() { this.Total += 1; } // 같은 Total 필드를
public void Remove() { this.Total -= 1; } // 둘 다 만짐
}
맞다 — 이것도 공통 결합이다.
다만 자연스러운 경우라 위험이 작다.
"언제 받아들일 만한가"는 더 깊은 주제다(지금은 몰라도 된다).
예시 4 (독립적용)
두 서비스가 같은 Redis 키 session:123을 둘 다 읽고 쓴다.
몇 단계?
공통 결합이다. 전역 저장소(Redis)를 통째로 공유하니까.
왜 위험한가 (짧게)
- 필요 없는 것까지 다 알아야 함 → 통합 계약이 흐릿하다.
- 누가 언제 바꿨는지 추적 불가 → 부작용 출처를 못 찾는다.
- 검증 로직(예: "잔액은 0 이상")이 모든 모듈에 복사돼야 함.
- 동시에 쓰면 줄 세우기(직렬화)가 필요 → 느려지고 경쟁 조건 발생.
콘텐츠 결합과 다른 점
공통 결합은 모두가 합의하고 같은 판을 쓴다.
콘텐츠 결합은 상위가 모르거나 동의 안 한 채로 침범당한다.
단순 규칙 — 전역 데이터를 통째로 같이 만지면 공통 결합. 기본적으로 줄인다.
개념 3. 외부 결합 — 공동 우편함 한 칸만
망가지는 장면
공동 게시판이 너무 잡다하다.
그래서 두 집 사이에 우편함 한 칸만 두고 꼭 필요한 쪽지만 주고받기로 한다.
좁아졌지만, 여전히 공유 공간이라 우편함이 망가지면 둘 다 곤란하다.
일상비유
공동 우편함 한 칸.
게시판(공통)보다 좁지만 공유 공간이라는 본질은 같다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 공동 게시판(공통) | 변수 4개 전부 공유 | 너무 많이 공유 |
| 우편함 한 칸(외부) | Config.name 1개만 공유 |
좁지만 부작용은 남음 |
정의 — 공통 결합과 똑같이 전역 데이터로 통신하되, 공유하는 데이터의 양이 줄어든 형태.
예시 1 (worked): PL/I EXTERNAL (역사 원형)
PL/I에는 EXTERNAL이라는 표시가 있었다.
두 프로시저가 변수 A 하나를 같은 메모리에서 공유했다.
// ProcA 와 ProcB 가 같은 A 한 개를 가리킴
var external_A = [0]; // 같은 메모리(리스트로 흉내)
void ProcA() { external_A[0] = 5; } // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
void ProcB() { Console.WriteLine(external_A[0]); } // 5 가 보임 — 같은 곳을 봄
ProcA가 A를 바꾸면 ProcB의 A도 같이 바뀐다.
예시 2 (worked): 현대판 — 전역 정적 변수
public class Config // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
var name = ""; // 전역으로 공유되는 한 개 값
}
void SetName(string n) { Config.Name = n; } // 한 클래스가 씀
void Greet() { Console.WriteLine($"Hi {Config.Name}"); } // 다른 클래스가 읽음
공유하는 건 name 하나뿐이다.
게시판(공통)보다 좁다.
예시 3 (부분완성): 공통 vs 외부 구분
아래 둘 중 외부 결합은?
var shared_all = {"a":1,"b":2,"c":3,"d":4}; // (A) 통째로 공유
Config.Timezone = "KST"; // (B) 한 값만 공유
(B)가 외부 결합이다.
필요한 한 값만 공유하기 때문.
(A)는 통째로 공유하니 공통 결합.
예시 4 (독립적용)
여러 클래스가 같은 static 카운터 하나를 같이 증가시킨다.
몇 단계?
외부 결합이다. 전역 변수 1개만 공유하니까.
공통과 비교
| 측면 | 공통 결합 | 외부 결합 |
|---|---|---|
| 공유 데이터 양 | 전부 | 필요한 것만 |
| 통신 방식 | 전역 메모리 | 전역 메모리(같음) |
| 동시 수정 위험 | 큼 | 큼 |
| 부작용 추적 | 어려움 | 어려움 |
| 등급 | 더 강함 | 조금 약함 |
핵심: 공유 폭은 좁아졌지만, 부작용 추적·동시 수정·검증 중복 문제는 그대로다.
단순 규칙 — 전역 데이터를 일부만 공유해도 여전히 외부 결합. 공통보다 낫지만 전역은 전역이다.
개념 4. 제어 결합 — 조리법까지 지시
망가지는 장면
식당에 "스테이크 주세요"가 아니라 "주물팬에 센 불 2분, 약불 5분, 양파 빼고"라고 조리법까지 시킨다.
식당이 조리법을 바꾸려면 나한테 일일이 전화해야 한다.
일상비유
주방에 어떻게 만들지까지 지시.
"무엇"만 시키면 되는데 "어떻게"까지 끼어든다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 조리법까지 지시 | send(type="sms", msg) |
내부 분기 바꾸면 호출자도 깨짐 |
| 메뉴만 주문 | notify_user(user, msg) |
안전 — 의도만 전달 |
정의 — 한 부품이 플래그·명령·옵션을 넘겨 다른 부품의 내부 실행 분기를 직접 조종하는 것.
type·mode·flag·action 같은 분기 키워드가 신호다.
예시 1 (worked): 잘못된 예 (before)
static dynamic SendNotification(dynamic type, string message) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
if type == "sms": send_sms(message); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
elif type == "email": send_email(message); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
elif type == "push": send_push(message); // 호출자가 이 3개를 다 알아야 함
}
static dynamic NotifyUser(dynamic user, string message) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
var t = "sms" if user.WantsSms else "email"; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
send_notification(t, message); // 내부 분기 키워드를 호출자가 들고 있음
}
send_notification이 push를 빼면, push를 넘기던 모든 호출자가 런타임 에러.
호출자가 내부 분기 종류를 알아야 한다는 것 자체가 강한 결합이다.
예시 2 (worked): 올바른 예 (after) — 의도만 노출
static dynamic NotifyUser(dynamic user, string message) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
var channel = pick_channel(user); // 채널 선택은 내부에서 — 바깥에 안 보임
channel.Send(message); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
}
notify_user(user, "안녕"); // 호출자는 분기 키워드를 모름
호출자는 "사용자에게 알려"라는 의도만 안다.
채널이 바뀌어도 호출자는 손댈 게 없다.
예시 3 (부분완성): 제어 결합 찾기
process(item, "discount"); // (A)
apply_discount(item); // (B)
(A)가 제어 결합이다.
문자열 "discount"가 내부 분기를 결정하니까.
(B)는 의도만 전달 — 더 낮은 등급.
예시 4 (독립적용)
render(page, format="pdf")에서 format을 바꾸면 호출자도 다 고쳐야 한다.
몇 단계?
제어 결합이다. format 플래그가 내부 분기를 조종하니까.
제어 결합의 신호
- 인수에
type·mode·flag·action같은 분기 키워드가 보임. - 호출자가 내부 case를 알고 호출해야 함.
- case를 추가·삭제하면 호출 코드까지 같이 바꿔야 함.
외부 결합과 비교
제어 결합은 전역 상태 대신 명시적 인수로 통신한다 → 외부 결합보다 한 단계 낮다.
하지만 상위가 기능을 완전히 못 숨긴다는 점에서 여전히 강한 편이다.
단순 규칙 — 호출자가 "어떻게 할지"(내부 분기)를 알아야 하면 제어 결합. 의도만 받게 바꾼다.
개념 5. 스탬프 결합 — 명함 통째로 건넴
망가지는 장면
"이름만 알려 줘"라 했는데 명함 한 장을 통째로 준다.
이름·전화·이메일·주소·회사·직책이 다 적혀 있다.
나는 이름만 보지만, 명함 양식이 바뀌면 명함 받은 모든 사람을 신경 써야 한다.
일상비유
명함 한 장 통째.
이름 한 줄 필요한데 전부 다 받는다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 명함 통째 | get(id).status |
객체 양식 바꾸면 다 영향 |
| 이름만 받음 | get_status(id) |
안전 — 필요한 값만 |
정의 — 한두 필드만 쓰려는데 상위가 수십 개 필드를 가진 객체 전체를 넘기는 것.
예시 1 (worked): 잘못된 예 (before)
var customer = repo.Get(customer_id); // Customer = 수백 개 필드
var status = customer.Status; // 정작 status 하나만 씀
Customer에 필드를 추가하거나 타입을 바꾸면 모든 소비자가 잠재적으로 영향받는다.
CRM 작성자는 "혹시 누가 이 필드 쓰나" 늘 걱정해야 한다.
예시 2 (worked): 같은 위험, 다른 모습
// 화면이 "고객 이름"만 필요한데 고객 객체 전체를 넘겨받음
static dynamic Render(dynamic customer) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
Console.WriteLine(customer.Name); // name 하나만 씀
}
화면은 name만 보지만 객체 전체에 묶여 있다.
예시 3 (부분완성): 스탬프 vs 데이터 구분
draw(get_user(id)); // (A) 유저 객체 통째
draw(get_user(id).Avatar); // (B) avatar 하나만
(A)가 스탬프 결합 — 통째로 넘김.
(B)는 필요한 값만 → 더 낮은 데이터 결합.
예시 4 (독립적용)
리포트 함수가 50개 필드짜리 Order 객체를 받아 total 하나만 쓴다.
몇 단계?
스탬프 결합이다.
공통 결합과 다른 점
스탬프 결합은 전역으로 수정 가능한 게 아니라 메서드 호출로 데이터를 받는다.
비즈니스 로직 공유 없음, 동시성 문제 없음.
단지 경계가 헐겁다(필요 없는 걸 너무 많이 받는다)는 게 문제다.
제어 결합과 다른 점
제어 결합은 기능·로직에 대한 지식을 공유한다.
스탬프 결합은 데이터 구조에 대한 지식을 공유한다.
데이터 구조 지식이 행동 지식보다 더 안정적이라 스탬프가 한 단계 더 낮다.
단순 규칙 — 큰 객체를 받아 일부만 쓰면 스탬프 결합. 필요한 값만 받게 줄인다.
개념 6. 데이터 결합 — 이름 한 단어만 (가장 안전)
망가지는 장면
(여긴 망가지지 않는다. 가장 안전한 등급이라서다.)
"이름만 알려 줘"라 하니 이름 한 단어만 적은 메모를 준다.
명함 양식이 바뀌어도 나에겐 영향 없다.
우리 사이엔 "이름 한 단어"라는 작은 약속만 있다.
일상비유
이름 한 단어만 적은 종이.
필요한 것만, 깔끔하게.
| 비유 | 코드 | 위험 |
|---|---|---|
| 이름 한 단어 | get_status(id) |
거의 안 깨짐 |
| 포장지(DTO) | get_snapshot(id) |
내부 바뀌어도 바깥 안전 |
정의 — 통합에 꼭 필요한 최소 데이터만 주고받는 것. 비즈니스 로직도, 불필요 필드도 공유하지 않는다.
예시 1 (worked): 필요한 값만 반환 (after)
스탬프 결합 예시를 한 단계 낮춰 보자.
// before(스탬프): 객체 통째 받아 status 만 씀
var status = repo.Get(id).Status; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
// after(데이터): status 만 주는 메서드로 분리
var status = repo.GetStatus(id); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
Customer 내부 구조가 어떻게 바뀌든, get_status의 반환만 안정적이면 소비자는 멀쩡하다.
예시 2 (worked): DTO로 내부·외부 분리 (극단판)
public class CustomerSnapshot // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public string Name { get; init; } // 외부에 보여 줄 읽기용 값입니다.
@staticmethod; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
public dynamic Of(dynamic customer) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return new CustomerSnapshot(customer.Name); // 결과를 호출자에게 돌려줍니다.
}
}
static dynamic Get(dynamic id) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return CustomerSnapshot.Of(fetch(id)); // 내부 Customer 가 아니라 포장지를 줌
}
내부 Customer는 마음껏 진화시켜도 된다.
바깥은 CustomerSnapshot이라는 안정된 약속만 본다.
내부(자주 변함)와 외부 약속(잘 안 변함)을 분리한 것.
예시 3 (부분완성): 데이터 결합으로 격상
Analysis가 Customer(30필드)에서 email만 쓴다.
데이터 결합으로 올리려면?
// 빈칸: 필요한 값만 주는 메서드
static dynamic Get(dynamic id) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return fetch(id).Email; // 결과를 호출자에게 돌려줍니다.
}
get_email(id)로 분리하면 Analysis는 나머지 29필드를 모른다.
예시 4 (독립적용)
get_total(order_id)가 합계 숫자 하나만 반환한다.
몇 단계?
데이터 결합이다. 가장 안전.
왜 가장 안전한가
- 공유 지식이 최소 → 변경 전파가 가장 적다.
- 항상 명시적 공개 인터페이스로만 통합한다.
- 내부를 바꿔도 바깥 약속이 안 변하면 소비자가 안 깨진다.
단순 규칙 — 새 코드의 기본값은 데이터 결합. 더 강한 결합이 필요하면 이유를 댈 수 있어야 한다.
6수준 한 줄 묶음표
| 수준 | 일상 비유 | 핵심 신호 | 공유 지식 |
|---|---|---|---|
| 콘텐츠 (강) | 벽 뚫고 들어옴 | 비공개 침범·남의 DB 직접 접속 | 구현 세부 전부 |
| 공통 | 공동 게시판 | 전역 변수·공유 파일·공유 DB | 전역 데이터 전부 |
| 외부 | 우편함 한 칸 | static 변수·EXTERNAL | 전역 데이터 일부 |
| 제어 | 조리법까지 지시 | type·mode·flag 인수 |
내부 로직 |
| 스탬프 | 명함 통째 | 큰 객체에서 1개만 씀 | 데이터 구조 |
| 데이터 (약) | 이름 한 단어 | 필요한 값만·DTO | 최소 데이터 |
위로 갈수록: 공유 지식↑ · 변경 전파↑ · 캡슐화 깨짐↑
아래로 갈수록: 공유 지식↓ · 인터페이스 명확↑ · 변경 안전↑
5.4 요점 — 코드에서 등급 찾기
지금은 "이 설계가 좋냐 나쁘냐"를 판단하는 단계가 아니다.
경계에서 어떤 종류의 지식이 새 나가는지를 알아보는 단계다.
코드를 볼 때 세 가지만 물어보면 된다.
- 공유 DB·공유 파일을 같이 쓰는 부품이 있나? (외부·공통 결합)
- 데이터 구조를 통째로 주고받는 인터페이스가 있나? (스탬프 대 데이터 결합)
- 남의 구현 세부에 의존해 통합하는 곳이 있나? (콘텐츠 결합)
이 세 질문으로 코드를 훑으면 등급이 보인다.
정리
- 같은 "연결"도 등급이 있다. 콘텐츠 → 공통 → 외부 → 제어 → 스탬프 → 데이터 (강 → 약).
- 위로 갈수록 서로 많이 알고, 변경이 멀리 번진다. 아래로 갈수록 안전하다.
- 기본값은 가장 안전한 데이터 결합. 더 강한 게 필요하면 이유를 댄다.
한 걸음 더 ▸ (지금 몰라도 됨)
"공통·외부 결합 = 무조건 나쁘다"는 아니다. 같은 클래스 안 메서드들이 멤버 변수를 같이 만지는 건 자연스러운 공통 결합이다. 같은 등급이라도 언제 받아들일 만한지는 더 깊은 주제라, 지금은 "적게 알수록 안전" 규칙 하나만 들고 가면 된다.
더 해보기
분산 시스템에서 콘텐츠 결합은 "남의 서비스 DB 직접 접근"으로 자주 나타난다. 회피책은 Database per Service 패턴 (검증 2026-05-31).
다음 6장 예고 — 또 다른 시대가 만든 결합 모델(공생성)을 본다. (지금 몰라도 됩니다 — 6장에서 천천히 풀려요.)
연습문제
- 설명.
결합에도 등급이 있다의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라. - 구분. 두 개념(
내용 결합,데이터 결합)을 실제 예시 하나로 구분하라. - 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.
부록 A. 쉬운 용어 사전
| 한글 용어 | 원문 영문명 | 아주 쉬운 뜻 | 이 장에서 나온 위치 |
|---|---|---|---|
| 내용 결합 | Content Coupling | 다른 부품의 내부 구현을 직접 만지는 가장 위험한 결합. | 부록 B와 본문 예시 |
| 데이터 결합 | Data Coupling | 필요한 값만 주고받는 비교적 가벼운 결합. | 부록 B와 본문 예시 |
| 스탬프 결합 | Stamp Coupling | 필요한 조각보다 큰 데이터 덩어리를 통째로 넘기는 결합. | 부록 B와 본문 예시 |
부록 B. 헷갈리는 개념 비교표
| A | B | 구분 포인트 |
|---|---|---|
| 내용 결합 | 데이터 결합 | 내용 결합은 속을 직접 만지고, 데이터 결합은 필요한 값만 주고받는다. |
| 스탬프 결합 | 데이터 결합 | 스탬프는 덩어리째 넘기고, 데이터는 필요한 조각만 넘긴다. |
부록 C. 더 읽을 자료
- 이 장의
더 해보기섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다. - 같은 책의
0장 한눈에 보기— 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다. - 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
- 이 장의
flashcards.json— 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.
부록 D. 연습문제 풀이
- 설명 예시.
결합에도 등급이 있다는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다. - 구분 예시. 두 개념(
내용 결합,데이터 결합)의 차이는 이렇게 잡으면 된다. 내용 결합은 속을 직접 만지고, 데이터 결합은 필요한 값만 주고받는다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다. - 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
클릭하거나 Space를 눌러 뒤집기